package pj.byt;

import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.oauth2.SaOAuth2Manager;
import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Consts;
import cn.dev33.satoken.oauth2.model.*;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaFoxUtil;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;

import java.util.List;

/**
 * MySaOAuth2Template
 *
 * @author grl
 * @date 2023/8/24
 */
public class MySaOAuth2Template {
    public MySaOAuth2Template() {
    }

    public SaClientModel getClientModel(String clientId) {
        return null;
    }

    public String getOpenid(String clientId, Object loginId) {
        return null;
    }

    public SaClientModel checkClientModel(String clientId) {
        SaClientModel clientModel = this.getClientModel(clientId);
        if (clientModel == null) {
            throw new SaOAuth2Exception("无效client_id: " + clientId);
        } else {
            return clientModel;
        }
    }

    public AccessTokenModel checkAccessToken(String accessToken) {
        AccessTokenModel at = this.getAccessToken(accessToken);
        SaOAuth2Exception.throwBy(at == null, "无效access_token：" + accessToken);
        return at;
    }

    public ClientTokenModel checkClientToken(String clientToken) {
        ClientTokenModel ct = this.getClientToken(clientToken);
        SaOAuth2Exception.throwBy(ct == null, "无效：client_token" + ct);
        return ct;
    }

    public Object getLoginIdByAccessToken(String accessToken) {
        return this.checkAccessToken(accessToken).loginId;
    }

    public void checkScope(String accessToken, String... scopes) {
        if (scopes != null && scopes.length != 0) {
            AccessTokenModel at = this.checkAccessToken(accessToken);
            List<String> scopeList = SaFoxUtil.convertStringToList(at.scope);
            String[] var5 = scopes;
            int var6 = scopes.length;

            for(int var7 = 0; var7 < var6; ++var7) {
                String scope = var5[var7];
                SaOAuth2Exception.throwBy(!scopeList.contains(scope), "该 Access-Token 不具备 Scope：" + scope);
            }

        }
    }

    public RequestAuthModel generateRequestAuth(SaRequest req, JSONObject jsonObject, Object loginId) {
        RequestAuthModel ra = new RequestAuthModel();
        ra.clientId = req.getParamNotNull(SaOAuth2Consts.Param.client_id);
        ra.responseType = jsonObject.getString(SaOAuth2Consts.Param.response_type);
        String redirectUri = jsonObject.getString(SaOAuth2Consts.Param.redirect_uri);
        if(StringUtils.isBlank(redirectUri)){
            redirectUri = req.getParamNotNull(SaOAuth2Consts.Param.redirect_uri);
        }
        ra.redirectUri = redirectUri;
        ra.state = jsonObject.getString(SaOAuth2Consts.Param.state);
        String scope = jsonObject.getString(SaOAuth2Consts.Param.scope);
        if(StringUtils.isBlank(scope)){
            scope = req.getParam(SaOAuth2Consts.Param.scope, "");
        }
        ra.scope = scope;
        ra.loginId = loginId;
        return ra;
    }

    public CodeModel generateCode(RequestAuthModel ra) {
        this.deleteCode(this.getCodeValue(ra.clientId, ra.loginId));
        String code = this.randomCode(ra.clientId, ra.loginId, ra.scope);
        CodeModel cm = new CodeModel(code, ra.clientId, ra.scope, ra.loginId, ra.redirectUri);
        this.saveCode(cm);
        this.saveCodeIndex(cm);
        return cm;
    }

    public AccessTokenModel generateAccessToken(String code) {
        CodeModel cm = this.getCode(code);
        SaOAuth2Exception.throwBy(cm == null, "无效code");
        this.deleteAccessToken(this.getAccessTokenValue(cm.clientId, cm.loginId));
        this.deleteRefreshToken(this.getRefreshTokenValue(cm.clientId, cm.loginId));
        AccessTokenModel at = this.convertCodeToAccessToken(cm);
        RefreshTokenModel rt = this.convertAccessTokenToRefreshToken(at);
        at.refreshToken = rt.refreshToken;
        at.refreshExpiresTime = rt.expiresTime;
        this.saveAccessToken(at);
        this.saveAccessTokenIndex(at);
        this.saveRefreshToken(rt);
        this.saveRefreshTokenIndex(rt);
        this.deleteCode(code);
        this.deleteCodeIndex(cm.clientId, cm.loginId);
        return at;
    }

    public AccessTokenModel refreshAccessToken(String refreshToken) {
        RefreshTokenModel rt = this.getRefreshToken(refreshToken);
        SaOAuth2Exception.throwBy(rt == null, "无效refresh_token: " + refreshToken);
        if (this.checkClientModel(rt.clientId).getIsNewRefresh()) {
            this.deleteRefreshToken(rt.refreshToken);
            rt = this.convertRefreshTokenToRefreshToken(rt);
            this.saveRefreshToken(rt);
            this.saveRefreshTokenIndex(rt);
        }

        this.deleteAccessToken(this.getAccessTokenValue(rt.clientId, rt.loginId));
        AccessTokenModel at = this.convertRefreshTokenToAccessToken(rt);
        this.saveAccessToken(at);
        this.saveAccessTokenIndex(at);
        return at;
    }

    public AccessTokenModel generateAccessToken(RequestAuthModel ra, boolean isCreateRt) {
        this.deleteAccessToken(this.getAccessTokenValue(ra.clientId, ra.loginId));
        if (isCreateRt) {
            this.deleteRefreshToken(this.getRefreshTokenValue(ra.clientId, ra.loginId));
        }

        String newAtValue = this.randomAccessToken(ra.clientId, ra.loginId, ra.scope);
        AccessTokenModel at = new AccessTokenModel(newAtValue, ra.clientId, ra.loginId, ra.scope);
        at.openid = this.getOpenid(ra.clientId, ra.loginId);
        at.expiresTime = System.currentTimeMillis() + this.checkClientModel(ra.clientId).getAccessTokenTimeout() * 1000L;
        if (isCreateRt) {
            RefreshTokenModel rt = this.convertAccessTokenToRefreshToken(at);
            this.saveRefreshToken(rt);
            this.saveRefreshTokenIndex(rt);
        }

        this.saveAccessToken(at);
        this.saveAccessTokenIndex(at);
        return at;
    }

    public ClientTokenModel generateClientToken(String clientId, String scope) {
        this.deleteClientToken(this.getPastTokenValue(clientId));
        ClientTokenModel oldCt = this.getClientToken(this.getClientTokenValue(clientId));
        this.savePastTokenIndex(oldCt);
        SaClientModel cm = this.checkClientModel(clientId);
        if (oldCt != null && cm.getPastClientTokenTimeout() != -1L) {
            oldCt.expiresTime = System.currentTimeMillis() + cm.getPastClientTokenTimeout() * 1000L;
            this.saveClientToken(oldCt);
        }

        ClientTokenModel ct = new ClientTokenModel(this.randomClientToken(clientId, scope), clientId, scope);
        ct.expiresTime = System.currentTimeMillis() + cm.getClientTokenTimeout() * 1000L;
        this.saveClientToken(ct);
        this.saveClientTokenIndex(ct);
        return ct;
    }

    public String buildRedirectUri(String redirectUri, String code, String state) {
        String url = SaFoxUtil.joinParam(redirectUri, SaOAuth2Consts.Param.code, code);
        if (!SaFoxUtil.isEmpty(state)) {
            url = SaFoxUtil.joinParam(url, SaOAuth2Consts.Param.state, state);
        }

        return url;
    }

    public String buildImplicitRedirectUri(String redirectUri, String token, String state) {
        String url = SaFoxUtil.joinSharpParam(redirectUri, SaOAuth2Consts.Param.token, token);
        if (!SaFoxUtil.isEmpty(state)) {
            url = SaFoxUtil.joinSharpParam(url, SaOAuth2Consts.Param.state, state);
        }

        return url;
    }

    public void revokeAccessToken(String accessToken) {
        AccessTokenModel at = this.getAccessToken(accessToken);
        if (at != null) {
            this.deleteAccessToken(accessToken);
            this.deleteAccessTokenIndex(at.clientId, at.loginId);
            String refreshToken = this.getRefreshTokenValue(at.clientId, at.loginId);
            this.deleteRefreshToken(refreshToken);
            this.deleteRefreshTokenIndex(at.clientId, at.loginId);
        }
    }

    public boolean isGrant(Object loginId, String clientId, String scope) {
        List<String> grantScopeList = SaFoxUtil.convertStringToList(this.getGrantScope(clientId, loginId));
        List<String> scopeList = SaFoxUtil.convertStringToList(scope);
        return scopeList.size() == 0 || grantScopeList.containsAll(scopeList);
    }

    public void checkContract(String clientId, String scope) {
        List<String> clientScopeList = SaFoxUtil.convertStringToList(this.checkClientModel(clientId).contractScope);
        List<String> scopelist = SaFoxUtil.convertStringToList(scope);
        if (!clientScopeList.containsAll(scopelist)) {
            throw new SaOAuth2Exception("请求的Scope暂未签约");
        }
    }

    public void checkRightUrl(String clientId, String url) {
        if (!SaFoxUtil.isUrl(url)) {
            throw new SaOAuth2Exception("无效redirect_url：" + url);
        } else {
            int qIndex = url.indexOf("?");
            if (qIndex != -1) {
                url = url.substring(0, qIndex);
            }

            List<String> allowList = SaFoxUtil.convertStringToList(this.checkClientModel(clientId).allowUrl);
            if (!(Boolean) SaStrategy.me.hasElement.apply(allowList, url)) {
                throw new SaOAuth2Exception("非法redirect_url：" + url);
            }
        }
    }

    public SaClientModel checkClientSecret(String clientId, String clientSecret) {
        SaClientModel cm = this.checkClientModel(clientId);
        SaOAuth2Exception.throwBy(cm.clientSecret == null || !cm.clientSecret.equals(clientSecret), "无效client_secret: " + clientSecret);
        return cm;
    }

    public CodeModel checkGainTokenParam(String code, String clientId, String clientSecret, String redirectUri) {
        CodeModel cm = this.getCode(code);
        SaOAuth2Exception.throwBy(cm == null, "无效code: " + code);
        SaOAuth2Exception.throwBy(!cm.clientId.equals(clientId), "无效client_id: " + clientId);
        String dbSecret = this.checkClientModel(clientId).clientSecret;
        SaOAuth2Exception.throwBy(dbSecret == null || !dbSecret.equals(clientSecret), "无效client_secret: " + clientSecret);
        if (!SaFoxUtil.isEmpty(redirectUri)) {
            SaOAuth2Exception.throwBy(!redirectUri.equals(cm.redirectUri), "无效redirect_uri: " + redirectUri);
        }

        return cm;
    }

    public RefreshTokenModel checkRefreshTokenParam(String clientId, String clientSecret, String refreshToken) {
        RefreshTokenModel rt = this.getRefreshToken(refreshToken);
        SaOAuth2Exception.throwBy(rt == null, "无效refresh_token: " + refreshToken);
        SaOAuth2Exception.throwBy(!rt.clientId.equals(clientId), "无效client_id: " + clientId);
        String dbSecret = this.checkClientModel(clientId).clientSecret;
        SaOAuth2Exception.throwBy(dbSecret == null || !dbSecret.equals(clientSecret), "无效client_secret: " + clientSecret);
        return rt;
    }

    public AccessTokenModel checkAccessTokenParam(String clientId, String clientSecret, String accessToken) {
        AccessTokenModel at = this.checkAccessToken(accessToken);
        SaOAuth2Exception.throwBy(!at.clientId.equals(clientId), "无效client_id：" + clientId);
        this.checkClientSecret(clientId, clientSecret);
        return at;
    }

    public AccessTokenModel convertCodeToAccessToken(CodeModel cm) {
        AccessTokenModel at = new AccessTokenModel();
        at.accessToken = this.randomAccessToken(cm.clientId, cm.loginId, cm.scope);
        at.clientId = cm.clientId;
        at.loginId = cm.loginId;
        at.scope = cm.scope;
        at.openid = this.getOpenid(cm.clientId, cm.loginId);
        at.expiresTime = System.currentTimeMillis() + this.checkClientModel(cm.clientId).getAccessTokenTimeout() * 1000L;
        return at;
    }

    public RefreshTokenModel convertAccessTokenToRefreshToken(AccessTokenModel at) {
        RefreshTokenModel rt = new RefreshTokenModel();
        rt.refreshToken = this.randomRefreshToken(at.clientId, at.loginId, at.scope);
        rt.clientId = at.clientId;
        rt.loginId = at.loginId;
        rt.scope = at.scope;
        rt.openid = at.openid;
        rt.expiresTime = System.currentTimeMillis() + this.checkClientModel(at.clientId).getRefreshTokenTimeout() * 1000L;
        at.refreshToken = rt.refreshToken;
        at.refreshExpiresTime = rt.expiresTime;
        return rt;
    }

    public AccessTokenModel convertRefreshTokenToAccessToken(RefreshTokenModel rt) {
        AccessTokenModel at = new AccessTokenModel();
        at.accessToken = this.randomAccessToken(rt.clientId, rt.loginId, rt.scope);
        at.refreshToken = rt.refreshToken;
        at.clientId = rt.clientId;
        at.loginId = rt.loginId;
        at.scope = rt.scope;
        at.openid = rt.openid;
        at.expiresTime = System.currentTimeMillis() + this.checkClientModel(rt.clientId).getAccessTokenTimeout() * 1000L;
        at.refreshExpiresTime = rt.expiresTime;
        return at;
    }

    public RefreshTokenModel convertRefreshTokenToRefreshToken(RefreshTokenModel rt) {
        RefreshTokenModel newRt = new RefreshTokenModel();
        newRt.refreshToken = this.randomRefreshToken(rt.clientId, rt.loginId, rt.scope);
        newRt.expiresTime = System.currentTimeMillis() + this.checkClientModel(rt.clientId).getRefreshTokenTimeout() * 1000L;
        newRt.clientId = rt.clientId;
        newRt.scope = rt.scope;
        newRt.loginId = rt.loginId;
        newRt.openid = rt.openid;
        return newRt;
    }

    public void saveCode(CodeModel c) {
        if (c != null) {
            SaManager.getSaTokenDao().setObject(this.splicingCodeSaveKey(c.code), c, SaOAuth2Manager.getConfig().getCodeTimeout());
        }
    }

    public void saveCodeIndex(CodeModel c) {
        if (c != null) {
            SaManager.getSaTokenDao().set(this.splicingCodeIndexKey(c.clientId, c.loginId), c.code, SaOAuth2Manager.getConfig().getCodeTimeout());
        }
    }

    public void saveAccessToken(AccessTokenModel at) {
        if (at != null) {
            SaManager.getSaTokenDao().setObject(this.splicingAccessTokenSaveKey(at.accessToken), at, at.getExpiresIn());
        }
    }

    public void saveAccessTokenIndex(AccessTokenModel at) {
        if (at != null) {
            SaManager.getSaTokenDao().set(this.splicingAccessTokenIndexKey(at.clientId, at.loginId), at.accessToken, at.getExpiresIn());
        }
    }

    public void saveRefreshToken(RefreshTokenModel rt) {
        if (rt != null) {
            SaManager.getSaTokenDao().setObject(this.splicingRefreshTokenSaveKey(rt.refreshToken), rt, rt.getExpiresIn());
        }
    }

    public void saveRefreshTokenIndex(RefreshTokenModel rt) {
        if (rt != null) {
            SaManager.getSaTokenDao().set(this.splicingRefreshTokenIndexKey(rt.clientId, rt.loginId), rt.refreshToken, rt.getExpiresIn());
        }
    }

    public void saveClientToken(ClientTokenModel ct) {
        if (ct != null) {
            SaManager.getSaTokenDao().setObject(this.splicingClientTokenSaveKey(ct.clientToken), ct, ct.getExpiresIn());
        }
    }

    public void saveClientTokenIndex(ClientTokenModel ct) {
        if (ct != null) {
            SaManager.getSaTokenDao().set(this.splicingClientTokenIndexKey(ct.clientId), ct.clientToken, ct.getExpiresIn());
        }
    }

    public void savePastTokenIndex(ClientTokenModel ct) {
        if (ct != null) {
            Long ttl = ct.getExpiresIn();
            SaClientModel cm = this.checkClientModel(ct.clientId);
            if (cm.getPastClientTokenTimeout() != -1L) {
                ttl = cm.getPastClientTokenTimeout();
            }

            SaManager.getSaTokenDao().set(this.splicingPastTokenIndexKey(ct.clientId), ct.clientToken, ttl);
        }
    }

    public void saveGrantScope(String clientId, Object loginId, String scope) {
        if (!SaFoxUtil.isEmpty(scope)) {
            long ttl = this.checkClientModel(clientId).getAccessTokenTimeout();
            SaManager.getSaTokenDao().set(this.splicingGrantScopeKey(clientId, loginId), scope, ttl);
        }

    }

    public CodeModel getCode(String code) {
        return code == null ? null : (CodeModel)SaManager.getSaTokenDao().getObject(this.splicingCodeSaveKey(code));
    }

    public String getCodeValue(String clientId, Object loginId) {
        return SaManager.getSaTokenDao().get(this.splicingCodeIndexKey(clientId, loginId));
    }

    public AccessTokenModel getAccessToken(String accessToken) {
        return accessToken == null ? null : (AccessTokenModel)SaManager.getSaTokenDao().getObject(this.splicingAccessTokenSaveKey(accessToken));
    }

    public String getAccessTokenValue(String clientId, Object loginId) {
        return SaManager.getSaTokenDao().get(this.splicingAccessTokenIndexKey(clientId, loginId));
    }

    public RefreshTokenModel getRefreshToken(String refreshToken) {
        return refreshToken == null ? null : (RefreshTokenModel)SaManager.getSaTokenDao().getObject(this.splicingRefreshTokenSaveKey(refreshToken));
    }

    public String getRefreshTokenValue(String clientId, Object loginId) {
        return SaManager.getSaTokenDao().get(this.splicingRefreshTokenIndexKey(clientId, loginId));
    }

    public ClientTokenModel getClientToken(String clientToken) {
        return clientToken == null ? null : (ClientTokenModel)SaManager.getSaTokenDao().getObject(this.splicingClientTokenSaveKey(clientToken));
    }

    public String getClientTokenValue(String clientId) {
        return SaManager.getSaTokenDao().get(this.splicingClientTokenIndexKey(clientId));
    }

    public String getPastTokenValue(String clientId) {
        return SaManager.getSaTokenDao().get(this.splicingPastTokenIndexKey(clientId));
    }

    public String getGrantScope(String clientId, Object loginId) {
        return SaManager.getSaTokenDao().get(this.splicingGrantScopeKey(clientId, loginId));
    }

    public void deleteCode(String code) {
        if (code != null) {
            SaManager.getSaTokenDao().deleteObject(this.splicingCodeSaveKey(code));
        }

    }

    public void deleteCodeIndex(String clientId, Object loginId) {
        SaManager.getSaTokenDao().delete(this.splicingCodeIndexKey(clientId, loginId));
    }

    public void deleteAccessToken(String accessToken) {
        if (accessToken != null) {
            SaManager.getSaTokenDao().deleteObject(this.splicingAccessTokenSaveKey(accessToken));
        }

    }

    public void deleteAccessTokenIndex(String clientId, Object loginId) {
        SaManager.getSaTokenDao().delete(this.splicingAccessTokenIndexKey(clientId, loginId));
    }

    public void deleteRefreshToken(String refreshToken) {
        if (refreshToken != null) {
            SaManager.getSaTokenDao().deleteObject(this.splicingRefreshTokenSaveKey(refreshToken));
        }

    }

    public void deleteRefreshTokenIndex(String clientId, Object loginId) {
        SaManager.getSaTokenDao().delete(this.splicingRefreshTokenIndexKey(clientId, loginId));
    }

    public void deleteClientToken(String clientToken) {
        if (clientToken != null) {
            SaManager.getSaTokenDao().deleteObject(this.splicingClientTokenSaveKey(clientToken));
        }

    }

    public void deleteClientTokenIndex(String clientId) {
        SaManager.getSaTokenDao().delete(this.splicingClientTokenIndexKey(clientId));
    }

    public void deletePastTokenIndex(String clientId) {
        SaManager.getSaTokenDao().delete(this.splicingPastTokenIndexKey(clientId));
    }

    public void deleteGrantScope(String clientId, Object loginId) {
        SaManager.getSaTokenDao().delete(this.splicingGrantScopeKey(clientId, loginId));
    }

    public String randomCode(String clientId, Object loginId, String scope) {
        return SaFoxUtil.getRandomString(60);
    }

    public String randomAccessToken(String clientId, Object loginId, String scope) {
        return SaFoxUtil.getRandomString(60);
    }

    public String randomRefreshToken(String clientId, Object loginId, String scope) {
        return SaFoxUtil.getRandomString(60);
    }

    public String randomClientToken(String clientId, String scope) {
        return SaFoxUtil.getRandomString(60);
    }

    public String splicingCodeSaveKey(String code) {
        return SaManager.getConfig().getTokenName() + ":oauth2:code:" + code;
    }

    public String splicingCodeIndexKey(String clientId, Object loginId) {
        return SaManager.getConfig().getTokenName() + ":oauth2:code-index:" + clientId + ":" + loginId;
    }

    public String splicingAccessTokenSaveKey(String accessToken) {
        return SaManager.getConfig().getTokenName() + ":oauth2:access-token:" + accessToken;
    }

    public String splicingAccessTokenIndexKey(String clientId, Object loginId) {
        return SaManager.getConfig().getTokenName() + ":oauth2:access-token-index:" + clientId + ":" + loginId;
    }

    public String splicingRefreshTokenSaveKey(String refreshToken) {
        return SaManager.getConfig().getTokenName() + ":oauth2:refresh-token:" + refreshToken;
    }

    public String splicingRefreshTokenIndexKey(String clientId, Object loginId) {
        return SaManager.getConfig().getTokenName() + ":oauth2:refresh-token-index:" + clientId + ":" + loginId;
    }

    public String splicingClientTokenSaveKey(String clientToken) {
        return SaManager.getConfig().getTokenName() + ":oauth2:client-token:" + clientToken;
    }

    public String splicingClientTokenIndexKey(String clientId) {
        return SaManager.getConfig().getTokenName() + ":oauth2:client-token-indedx:" + clientId;
    }

    public String splicingPastTokenIndexKey(String clientId) {
        return SaManager.getConfig().getTokenName() + ":oauth2:past-token-indedx:" + clientId;
    }

    public String splicingGrantScopeKey(String clientId, Object loginId) {
        return SaManager.getConfig().getTokenName() + ":oauth2:grant-scope:" + clientId + ":" + loginId;
    }
}
